package com.arvin.router.compiler;

import com.arvin.router.annotation.Route;
import com.arvin.router.compiler.util.Log;
import com.arvin.router.compiler.util.Utils;
import com.arvin.router.model.RouteMeta;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;


@AutoService(Processor.class)
//@SupportedAnnotationTypes("com.arvin.routerannotation.Router")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class RouteProcessor extends AbstractProcessor {
    private Filer filerUtils;

    private Elements elementsUtils;
    private javax.lang.model.util.Types typeUtils;
    private String moduleName;
    private Log log;
    private Map<String, List<RouteMeta>> groupMap = new HashMap<>();
    private Map<String, String> rootMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filerUtils = processingEnvironment.getFiler();
        elementsUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        log = Log.newLog(processingEnvironment.getMessager());
        log.i("------------------------------init  process----------------------------------------");
        Map<String, String> options = processingEnvironment.getOptions();
        if (!Utils.isEmpty(options)) {
            moduleName = options.get(Constants.ARGUMENTS_NAME);
        }
        log.i("RouteProcessor Paramters : " + moduleName);
        if (Utils.isEmpty(moduleName)) {
            throw new RuntimeException("Not set Processor Paramters");
        }

    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (Utils.isEmpty(set)) {
            return false;
        }
        Set<? extends Element> rountElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
        if (Utils.isEmpty(rountElements)) {
            return false;
        }
        processRoute(rountElements);
        return true;
    }

    /**
     * @param rountElements
     */
    private void processRoute(Set<? extends Element> rountElements) {
        TypeElement activity = elementsUtils.getTypeElement(Constants.ACTIVITY);
        TypeElement service = elementsUtils.getTypeElement(Constants.SERVICE);

        for (Element element : rountElements) {
            TypeMirror typeMirror = element.asType();
            log.i("Generate route class : " + typeMirror.toString());
            Route route = element.getAnnotation(Route.class);

            RouteMeta routeMeta;
            if (typeUtils.isSubtype(typeMirror, activity.asType())) {
                routeMeta = new RouteMeta(RouteMeta.Type.ACTIVITY, route, element);

            } else if (typeUtils.isSubtype(typeMirror, service.asType())) {
                routeMeta = new RouteMeta(RouteMeta.Type.SERVICE, route, element);
            } else {
                throw new RuntimeException("[Just support] Activity Route " + element);
            }

            categories(routeMeta);
            log.i("1111111111111111111111111111111111111111");

            log.i("33333333333333333333333333333333333333333");
        }
        TypeElement iRouteRoot = elementsUtils.getTypeElement(Constants.IROUTE_ROOT);
        log.i("4444444444444444444444444444444444  " + iRouteRoot);
        TypeElement iRouteGroup = elementsUtils.getTypeElement(Constants.IROUTE_GROUP);
        log.i("555555555555555555555 " + iRouteGroup);
        generateGroup(iRouteGroup);
        log.i("666666666666666666");
        generateRoot(iRouteGroup, iRouteRoot);
    }

    private void generateRoot(TypeElement iRouteGroup, TypeElement iRouteRoot) {
        ParameterizedTypeName routes = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
                )
        );
        ParameterSpec rootParamSpec = ParameterSpec.builder(routes, "routes")
                .build();
        MethodSpec.Builder loadIntoMethodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(rootParamSpec);
        for (Map.Entry<String, String> entry : rootMap.entrySet()) {
            loadIntoMethodBuilder.addStatement("routes.put($S,$T.class)",
                    entry.getKey(),
                    ClassName.get(Constants.PACKAGE_OF_GENERATE_FILE, entry.getValue()));
        }
        String rootClassName = Constants.NAME_OF_ROOT + moduleName;
        try {
            JavaFile.builder(Constants.PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(rootClassName)
                            .addModifiers(Modifier.PUBLIC)
                            .addSuperinterface(ClassName.get(iRouteRoot))
                            .addMethod(loadIntoMethodBuilder.build()).build())
                    .build().writeTo(filerUtils);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void generateGroup(TypeElement iRouteGroup) {
        ParameterizedTypeName parameterizedType = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouteMeta.class)
        );
        ParameterSpec atlas = ParameterSpec.builder(parameterizedType, "atlas").build();
        for (Map.Entry<String, List<RouteMeta>> entry : groupMap.entrySet()) {
            String group = entry.getKey();
            List<RouteMeta> routeMetaList = entry.getValue();
            MethodSpec.Builder builder = MethodSpec.methodBuilder("loadInto")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(TypeName.VOID)
                    .addParameter(atlas)
                    .addAnnotation(Override.class);

            for (RouteMeta routeMeta : routeMetaList) {

                builder.addStatement("atlas.put($S,$T.build($T.$L,$T.class,$S,$S))",
                        routeMeta.getPath(),
                        RouteMeta.class,
                        ClassName.get(RouteMeta.Type.class),
                        routeMeta.getType(),
                        ClassName.get((TypeElement) routeMeta.getElement()),
                        routeMeta.getPath(),
                        routeMeta.getGroup());
            }

            MethodSpec methodSpec = builder.build();
            log.i("66666666666666");
            String groupClassName = Constants.NAME_OF_GROUP + group;
            log.i(groupClassName);
            log.i(iRouteGroup.toString());
            TypeSpec typeSpec = TypeSpec.classBuilder(groupClassName)
                    .addSuperinterface(ClassName.get(iRouteGroup))
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(methodSpec)
                    .build();
            log.i("77777777777777777777");
            JavaFile javaFile = JavaFile.builder(Constants.PACKAGE_OF_GENERATE_FILE, typeSpec)
                    .build();
            log.i("88888888888888888");
            try {
                javaFile.writeTo(filerUtils);
            } catch (IOException e) {
                e.printStackTrace();
            }
            rootMap.put(entry.getKey(), groupClassName);
        }


    }

    /**
     * @param routeMeta
     */
    private void categories(RouteMeta routeMeta) {
        if (routeVerify(routeMeta)) {
            log.i("Group : " + routeMeta.getGroup() + " path : " + routeMeta.getPath());
            List<RouteMeta> routeMetas = groupMap.get(routeMeta.getGroup());
            if (routeMetas == null) {
                routeMetas = new ArrayList<>();
                routeMetas.add(routeMeta);
                groupMap.put(routeMeta.getGroup(), routeMetas);
            } else {
                routeMetas.add(routeMeta);
                groupMap.put(routeMeta.getGroup(), routeMetas);
                log.i("---------44444-----" + routeMetas);
            }
        } else {
            log.i("Group Info Error : " + routeMeta.getPath());
        }
    }

    /**
     * @return
     */
    private boolean routeVerify(RouteMeta routeMeta) {
        String path = routeMeta.getPath();
        String group = routeMeta.getGroup();
        if (!path.startsWith("/")) {
            return false;
        }
        if (Utils.isEmpty(group)) {
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            if (Utils.isEmpty(defaultGroup)) {
                return false;
            }
            routeMeta.setGroup(defaultGroup);
        }
        return true;
    }


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new HashSet<>();
        set.add(Route.class.getName());
        return set;
    }
}
